/**
 * @file bibserver.c
 * @brief Server del progetto BIBLIO
 * @author Marco Luisi
 * Si dichiara che il contenuto di questo file e' in ogni sua parte opera originale dell' autore.
*/
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "bib.h"
#include "biblio.h"
#include "bibserver.h"
#include "comsock.h"
#include "worker.h"
/**  Path del socket del server */
char sockpath[UNIX_PATH_MAX];
/**  File di log*/
FILE* logfile = NULL;
/** Data di fine prestito */
struct tm *loan_end;
/**  Mutex per la biblioteca */
pthread_mutex_t biblio_mtx = PTHREAD_MUTEX_INITIALIZER;
/**  Array per le schede bibliografiche */
scheda_t **biblio;
/**  Mutex per la gestione degli workers */
pthread_mutex_t workers_mtx = PTHREAD_MUTEX_INITIALIZER;
/**  Numero di libri della biblioteca */
int records = 0;
/** Server channel */
int sc;
/** Tabella di lookup comune agli workers */
const char* lookup_table[] = {_AUTORE, _TITOLO, _EDITORE, _LUOGO_PUBBLICAZIONE, _ANNO, _COLLOCAZIONE, _DESCRIZIONE_FISICA, _NOTA};
/** Variabile di controllo ricezione segnale */
volatile sig_atomic_t pending_signal = 0;
/**  Lista dei thread worker */
worker_t *wlist;
/** Dimensione della lista dei thread worker */
int wlist_dim;
/** Stringa loan per il file di log*/
char log_loan[] = "LOAN";
/** Stringa query per il file di log*/
char log_query[] = "QUERY";

/**
 * @brief Main del server biblio
 * @param argv Opzioni di lancio del client
 * @return Exit status del programma
 */
int main(int argc , char **argv) {
	worker_t *w;
	int exit_status = OK;
	/* Controllo dei parametri di avvio del server */
	if(argc != 3){
		fprintf(stderr,ERR_PARAM);
		return(EXIT_FAILURE);
	}
	/* Inizializzazione del server */
	if(init(argv[1], argv[2]) == ERROR){
		fprintf(stderr,ERR_INIT);
		exit(EXIT_FAILURE);
	}
	/* Server attivo fino alla ricezione di SIGINT o SIGTERM */
	exit_status += serve();
	if(closeServerChannel(sockpath, sc) == -1){
		exit_status += ERROR;
		perror(ERR_CLS);
	}
	/* Attesa terminazione dei thread worker attivi */
	w = wlist;
	while(w != NULL){//negli worker controllare se è sempre vivo il main, in caso contario far partire una funzione che fa lei la join
		pthread_join(*(w->tid),NULL);
		w = w->next;
	}
	exit_status += save(argv[1]);
	if (exit_status != OK){
		return (EXIT_FAILURE);
	}
	return (EXIT_SUCCESS);
}

/**
 * @brief Funzione di inizializzazione del server.
 *
 * Inizializzazione del server: apre i file e il socket, carica le schede bibliografiche.
 * Restituisce codice di errore in caso di problemi.
 *
 * @param bib_name nome della biblioteca
 * @param record_filename nome del file con i record da caricare
 * @return esito della inizializzazione.
 * @retval ERROR
 * @retval OK
 */
int init(char *bib_name, char *records_filename){
	/** File dei record da caricare */
	FILE* record_file = NULL;
	/** Path del socket del server */
	char logpath[UNIX_PATH_MAX];
	time_t t = time(0);

	EC_M1(signal_handling());
	/* Impostazione data di fine prestito */
	loan_end = localtime(&t);
	loan_end->tm_mon = loan_end->tm_mon +2;
	mktime(loan_end); /* mktime normalizza la data */

	/* Caricamento della biblioteca in memoria */
	if((record_file = fopen(records_filename, "r")) == NULL){
		perror(ERR_FILE);
		return(ERROR);
	}
	if((records = load_records(record_file,&biblio)) <= 0 ){
		perror(ERR_RECORD);
		return(ERROR);
	}
	fclose(record_file);
	/* Creazione directory tmp, socket e file di log */
	if (mkdir(TMPDIR, O_CREAT | 0666) == -1 && errno != EEXIST) {
		perror(ERR_FILE);
		return(ERROR);
	}
	sprintf(sockpath,"%s%s.sck",SOCKPATH,bib_name);
	if( access(sockpath, F_OK ) != -1 ) {
		perror(ERR_SOCK);
	    return(ERROR);
	}
	EC_M1(sc = createServerChannel(sockpath));
	sprintf(logpath,"./%s.log",bib_name);
	EC_NULL_M1(logfile = fopen(logpath,"w"));
	return(OK);
}

/**
 * @brief Funzione di salvataggio dei record
 *
 * Prima della effettiva scrittura su disco dei record, rinomina il file già esistente,
 * in modo da avere una copia di sicurezza in caso di errore in fase di scrittura del nuovo file.
 *
 * @param logfile file di log
 * @return res esito del salvataggio
 * @retval OK
 * @retval ERROR
 */
int save(char* bib_name) {
	FILE* bibfile = NULL;
	/** Nome del file temporaneo */
	char old_bib_name[UNIX_PATH_MAX];
	/** Contatore dei record salvati correttamente */
	int stored;
	/** Esito della funzione */
	int res = OK; /* Modificato solo in caso di errore */
	/* Si rinomina temporaneamente il file con i record presente */
	sprintf(old_bib_name, "%s.old", bib_name);
	EC_M1(rename(bib_name,old_bib_name));
	/* Salvataggio dei records */
	if ((bibfile = fopen(bib_name, "w")) == NULL ) {
		perror(ERR_FILE);
		rename(old_bib_name, bib_name); /* Ripristina il file precedente */
		res = ERROR;
	} else { /* File aperto, si salvano i records */
		stored = store_records(bibfile, biblio, records);
		if (stored < records) {
			res = ERROR;
			if (stored == -1) {
				fprintf(stderr, ERR_STR);
			} else {
				fprintf(stderr, ERR_SAV, stored);
			}
		}
		else { /* Salvataggio riuscito, cancella il file precedente */
			unlink(old_bib_name);
		}
	}
	fflush(logfile);
	if(fclose(logfile) == ERROR){
		perror(ERR_FCL);
		res = ERROR;
	}
	return (res);
}

/**
 * @brief Funzione che realizza il ciclo di funzionamento del server;
 * accettazione nuove connessioni dai client
 * creazione thread per servire le richieste
 * @return esito funzione
 * @retval OK
 * @retval ERROR
 */
int serve(void){
	int client_connection;
	worker_t *wpointer = NULL;
	worker_t *wpointer_prev = NULL;

	/* Ciclo di accettazione delle connessioni dai client */
	while(pending_signal == 0){
		client_connection = acceptConnection(sc);
		if(client_connection == -1){
			if (errno == EINTR){
				continue; /* Ricevuto un segnale, si passa a controllare pending_signal */
			}
			else{
				perror(ERR_ACPT);
				return (ERROR);
			}
		}
		/* Ricevuta una richiesta di connessione si attiva un nuovo worker per gestirla */
		pthread_mutex_lock(&workers_mtx);
		wpointer = wlist;
		/* Scorre la lista dei worker */
		while(wpointer != NULL){
			if(wpointer->tid == NULL){
				break;
			}
			wpointer_prev = wpointer;
			wpointer = wpointer->next;
		}
		if(wpointer == NULL){
			wpointer = malloc(sizeof(worker_t));
			wpointer->next = NULL;
			if(wpointer_prev != NULL){
				wpointer_prev->next = wpointer;
			}
		}
		wpointer->tid = malloc(sizeof(pthread_t));
		/* Primo slot libero nella lista */
		wpointer->client_connection = client_connection;
		if (pthread_create(wpointer->tid, NULL, worker_start, &wpointer->client_connection) != 0) {
			perror(ERR_THR);
			pthread_mutex_unlock(&workers_mtx);
			return(ERROR);
		}
		pthread_mutex_unlock(&workers_mtx);
	}
	return (OK);
}

/**
 * @brief Gestione dei segnali del server
 * @return esito della funzione
 * @retval OK
 * @retval ERROR
 */
int signal_handling(void){

	sigset_t set;
	struct sigaction *sa;

	/* Si mascherano tutti i segnali prima di installare gli opportuni gestori */
	EC_NULL_M1(sa = calloc(1,sizeof(struct sigaction)));
	EC_M1(sigfillset(&set))
	EC_M1(pthread_sigmask(SIG_SETMASK,&set,NULL))
	memset(&sa, sizeof(sa), 0);
	sa->sa_handler = SIG_IGN;
	EC_M1(sigaction(SIGQUIT, sa, NULL));

	/* Gestiori dei segnali */
	sa->sa_handler = sigterm_handler;
	EC_M1(sigaction(SIGTERM, sa, NULL));
	sa->sa_handler = sigint_handler;
	EC_M1(sigaction(SIGINT, sa, NULL));

	/* Rimozione della maschera */
	EC_M1(sigemptyset(&set));
	EC_M1(pthread_sigmask(SIG_SETMASK, &set, NULL));
	free(sa);
	return(OK);
}

/**
 * @brief Gestore del segnale SIGTERM
 * @param signum
 *
 * Setta la variabile pending_signal ad 1.
 *
 */
static void sigterm_handler(int signum){
	pending_signal = 1;
}

/**
 * @brief Gestore del segnale SIGINT
 * @param signum
 *
 * Setta la variabile pending_signal ad 1.
 *
 */
static void sigint_handler(int signum){
	pending_signal = 1;
}

